package com.mdd.front.service.impl;

import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.mdd.common.core.AjaxResult;
import com.mdd.common.core.PageResult;
import com.mdd.common.entity.coupon.Coupon;
import com.mdd.common.entity.coupon.CouponIssuing;
import com.mdd.common.enums.CouponEnum;
import com.mdd.common.enums.HttpEnum;
import com.mdd.common.mapper.order.CouponIssuingMapper;
import com.mdd.common.mapper.order.CouponMapper;
import com.mdd.common.util.*;
import com.mdd.common.vo.CouponConfigVo;
import com.mdd.front.LikeFrontThreadLocal;
import com.mdd.front.service.ICouponService;
import com.mdd.front.validate.commons.PageValidate;
import com.mdd.front.vo.coupon.CouponIssuingVo;
import com.mdd.front.vo.coupon.CouponVo;
import org.apache.commons.collections4.CollectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;

import javax.annotation.Resource;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 优惠券服务
 * @author shenjian
 * @createTime 2023年03月02日 00:21:00
 */
@Service
public class CouponServiceImpl implements ICouponService {

    private final Logger log = LoggerFactory.getLogger(CouponServiceImpl.class);

    private static final String REDIS_KEY_COUPON_PREFIX = "COUPON_";
    private static final String COUPON_RESULT_CODE_STR = "code";
    private static final String COUPON_RESULT_MSG_STR = "msg";
    private static final String COUPON_PRE_PAY = "0";
    private static final String COUPON_PAY_COMPLETE = "1";
    private static final String COUPON_FAIL = "-1";
    private static final String COUPON_LOADING = "-999";

    /** 领取优惠券分布式锁Key */
    private static final String COUPON_ISSUING_LOCK_KEY_PREFIX = "COUPON_ISSUING_LOCK";

    /** 领取优惠券分布式锁有效时间 */
    private static final Long COUPON_ISSUING_LOCK_REDIS_WAIT_TIME = 5L;
    @Resource
    CouponMapper couponMapper;

    @Resource
    CouponIssuingMapper couponIssuingMapper;

    @Override
    public PageResult<CouponVo> couponList(PageValidate pageValidate, Integer type) {
        Assert.isTrue(Arrays.asList(1,0).contains(type), "type参数只能为0,1");
        Integer userId = LikeFrontThreadLocal.getUserId();
        Integer pageNo   = pageValidate.getPageNo();
        Integer pageSize = pageValidate.getPageSize();

        Long now = TimeUtils.timestamp();

        QueryWrapper<Coupon> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        queryWrapper.orderByDesc("id");
        if (type == 0) {
            queryWrapper.eq("status", 0)
                    .lt("start_time", now)
                    .ge("end_time", now);
        } else {
            queryWrapper.in("status", Arrays.asList(1,2));
            queryWrapper.or(qw -> {
                qw.ge("start_time", now)
                        .lt("end_time", now);
            });
        }
        Page<Coupon> couponPage = this.couponMapper.selectPage(new Page<>(pageNo, pageSize), queryWrapper);
        // 更新已过期的优惠券
        updateInvalidCoupon(now);
        List<CouponVo> voList = new ArrayList<>();
        for (Coupon coupon: couponPage.getRecords()) {
            CouponVo vo = new CouponVo();
            BeanUtils.copyProperties(coupon, vo);
            vo.setCouponAmount(AmountUtils.changeF2Y(coupon.getCouponAmount()));
            vo.setReleaseTimeStr(TimeUtils.timestampToDate(coupon.getReleaseTime()));
            vo.setUseTimeStr(TimeUtils.timestampToDate(coupon.getUseTime()));
            vo.setStartTimeStr(TimeUtils.timestampToDate(coupon.getStartTime()));
            vo.setEndTimeStr(TimeUtils.timestampToDate(coupon.getEndTime()));
            voList.add(vo);
        }
        return PageResult.iPageHandle(couponPage.getTotal(), couponPage.getCurrent(), couponPage.getSize(), voList);
    }

    @Override
    @Async("receiveCouponExecutor")
    @Transactional(rollbackFor = Exception.class)
    public void receiveCoupon(String issuingSn, String redisKey, Integer userId) {
        String redidKey = REDIS_KEY_COUPON_PREFIX + issuingSn + "_" + userId;
        JSONObject json = new JSONObject();
        json.put(COUPON_RESULT_CODE_STR, COUPON_LOADING);
        RedisUtils.set(redidKey, json, 900);
        //尝试加锁
        String lockKey = COUPON_ISSUING_LOCK_KEY_PREFIX + issuingSn + "_" + userId;
        boolean lock = RedisLockUtils.tryLock(lockKey, issuingSn, Duration.ofSeconds(COUPON_ISSUING_LOCK_REDIS_WAIT_TIME));
        Assert.isTrue(lock, "请稍后再试");
        try {
            //继续执行后续流程
            List<CouponIssuing> couponIssuingList = this.couponIssuingMapper.selectList(
                    new QueryWrapper<CouponIssuing>().eq("sn", issuingSn));
            RedisUtils.set(redisKey, "-1", 5, TimeUnit.SECONDS);
            Assert.isTrue(couponIssuingList.size() == 1, "优惠券数据存在问题");
            CouponIssuing couponIssuing = couponIssuingList.get(0);
            Assert.isTrue((couponIssuing.getQuantity() > 0), "您来晚了，优惠券已领完！");
            // 生成优惠券
            Long count = this.couponMapper.selectCount(new QueryWrapper<Coupon>()
                    .eq("user_id", userId)
                    .eq("issuing_id", couponIssuing.getId()));
            Assert.isTrue(count == 0, "您已领取过该优惠券！");
            Long now = TimeUtils.timestamp();
            Coupon coupon = new Coupon();
            coupon.setType(CouponEnum.MANUAL.getKey());
            coupon.setUserId(userId);
            coupon.setIssuingId(couponIssuing.getId());
            coupon.setCouponName(couponIssuing.getCouponName());
            coupon.setCouponAmount(couponIssuing.getCouponAmount());
            coupon.setThreshold(couponIssuing.getThreshold());
            coupon.setStatus(0);
            coupon.setReleaseTime(now);
            coupon.setStartTime(now);
            coupon.setEndTime(TimeUtils.calculateEndTime(now, couponIssuing.getValidDate()));
            this.couponMapper.insert(coupon);
            // 减数量
            couponIssuing.setQuantity(couponIssuing.getQuantity() - 1);
            couponIssuingMapper.updateById(couponIssuing);
            json.put(COUPON_RESULT_CODE_STR, COUPON_PAY_COMPLETE);
            json.put(COUPON_RESULT_MSG_STR, AmountUtils.changeF2Y(couponIssuing.getCouponAmount()));
            //任务执行成功，清除锁
            RedisUtils.del(lockKey);
        } catch (MybatisPlusException mpException) {
            log.error("【领取优惠券异常报错 - [{}]】, errorMag - [{}]", issuingSn + "_" + userId, mpException);
            json.put(COUPON_RESULT_CODE_STR, COUPON_FAIL);
            json.put(COUPON_RESULT_MSG_STR, mpException.getMessage());
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        } catch (Throwable e) {
            log.error("业务处理发生异常，错误信息：", e);
            json.put(COUPON_RESULT_CODE_STR, COUPON_FAIL);
            json.put(COUPON_RESULT_MSG_STR, "领取优惠券异常报错, 请联系管理员！");
            //手动回滚事务
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        } finally {
            RedisUtils.set(redidKey, json, 900);
            //执行完毕之后，手动将锁释放
            RedisLockUtils.releaseLock(lockKey, issuingSn);
        }
        //任务执行成功，清除锁
        RedisUtils.del(lockKey);
    }

    @Override
    public CouponIssuingVo couponStatus(String issuingSn) {
        CouponIssuingVo vo = new CouponIssuingVo();
        Integer userId = LikeFrontThreadLocal.getUserId();
        List<CouponIssuing> couponIssuingList = this.couponIssuingMapper.selectList(
                new QueryWrapper<CouponIssuing>().eq("sn", issuingSn));
        Assert.isTrue(CollectionUtils.isNotEmpty(couponIssuingList), "优惠券发放数据有问题");
        CouponIssuing couponIssuing = couponIssuingList.get(0);
        Long count = this.couponMapper.selectCount(new QueryWrapper<Coupon>()
                .eq("user_id", userId)
                .eq("issuing_id", couponIssuing.getId()));
        BeanUtils.copyProperties(couponIssuing, vo);
        vo.setStatus(count > 0 ? 0 : 1);
        return vo;
    }

    /**
     * 查询缓存中的领取优惠券
     *
     * @param issuingSn 优惠券号
     * @return AjaxResult<Object>
     */
    @Override
    public AjaxResult<Object> receiveResultAsync(String issuingSn) {
        Integer userId = LikeFrontThreadLocal.getUserId();
        String redidKey = REDIS_KEY_COUPON_PREFIX + issuingSn + "_" + userId;
        Object object = RedisUtils.get(redidKey);
        if (null == object) {
            List<CouponIssuing> issuingList = this.couponIssuingMapper.selectList(new QueryWrapper<CouponIssuing>()
                    .eq("sn", issuingSn)
                    .last("limit 1"));
            if (CollectionUtils.isNotEmpty(issuingList)) {
                Long count = this.couponMapper.selectCount(new QueryWrapper<Coupon>()
                        .eq("user_id", userId)
                        .eq("issuing_id", issuingList.get(0).getId()));
                if (count > 0) {
                    return AjaxResult.success(HttpEnum.SUCCESS.getMsg(), "领取优惠券成功!");
                } else {
                    return AjaxResult.failed(3002, "优惠券领取失败!");
                }
            } else {
                return AjaxResult.failed(3002, "优惠券发放出现问题");
            }
        }
        JSONObject jsonObject = (JSONObject) object;
        if (jsonObject.get(COUPON_RESULT_CODE_STR).equals(COUPON_PAY_COMPLETE)) {
            return AjaxResult.success(HttpEnum.SUCCESS.getMsg(), jsonObject.get(COUPON_RESULT_MSG_STR));
        } else if (jsonObject.get(COUPON_RESULT_CODE_STR).equals(COUPON_LOADING)) {
            return AjaxResult.failed(3000, "生成预订单中....");
        } else {
            return AjaxResult.failed(3002, jsonObject.get(COUPON_RESULT_MSG_STR).toString());
        }
    }

    @Override
    @Async("issueSystemCouponExecutor")
    public void issueSystemCoupon(Integer userId, String type) {
        Long now = TimeUtils.timestamp();
        String couponConfigStr = ConfigUtils.get("coupon", type);
        Assert.notEmpty(couponConfigStr, "优惠券信息未配置");
        CouponConfigVo configVo = JSONObject.parseObject(couponConfigStr, CouponConfigVo.class);
        if (configVo.getCouponAmount().equals(0)) {
            return;
        }
        Coupon coupon = new Coupon();
        coupon.setUserId(userId);
        coupon.setCouponName(configVo.getCouponName());
        coupon.setCouponAmount(configVo.getCouponAmount());
        coupon.setType(type);
        coupon.setReleaseTime(now);
        coupon.setStatus(0);
        coupon.setThreshold(0);
        coupon.setStartTime(now);
        coupon.setEndTime(TimeUtils.calculateEndTime(now, configVo.getValidDate()));
        couponMapper.insert(coupon);
    }

    private void updateInvalidCoupon(Long now) {
        List<Coupon> coupons = this.couponMapper.selectList(new QueryWrapper<Coupon>()
                .eq("status", 0)
                .le("end_time", now));
        coupons.forEach(coupon -> {
            coupon.setStatus(2);
            this.couponMapper.updateById(coupon);
        });
    }


}
